/*
 * Copyright 2010 Christian Matzat and others
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package net.matzat.android.hiberdroid;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import net.matzat.android.hiberdroid.mapping.ColumnMapping;
import net.matzat.android.hiberdroid.mapping.EntityMapping;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Helper Class that initializes the database connection.
 * <p/>
 * The automatic database schema generation (CREATE TABLES) and the necessary updates on database schema change are done here.
 */
public class HiberDroidOpenHelper extends SQLiteOpenHelper {

    private static final String TAG = HiberDroidOpenHelper.class.getSimpleName();

    HiberDroidOpenHelper(Context context, String dbName, int dbVersion) {
        super(context, dbName, null, dbVersion);
    }

    /**
     * Creates the database from the mapping.
     *
     * @param db the database connection
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        for (EntityMapping mapping : Configuration.getEntities()) {
            createTable(db, mapping);
        }
    }

    /**
     * Updates the database to match a changed mapping. Adds new fields and drops tables, that are no longer used.
     * <p/>
     * Notice: Removed or renamed fields are currently not reflected to the database, because fields cannot removed from
     * SQLite tables. Also type changes are not possible, but this doesn't matter, because SQLite uses dynamic typing.
     *
     * @param db         the database connection
     * @param oldVersion version number of the old database version
     * @param newVersion version number of the new database version
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if (oldVersion > newVersion) {
            // downgrade
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, String.format("Downgrading database from version %d to %d , which will destroy all old data", oldVersion, newVersion));
            }
            for (EntityMapping mapping : Configuration.getEntities()) {
                db.execSQL(String.format("DROP TABLE IF EXISTS %s", mapping.getTable()));
            }
            onCreate(db);
        } else {
            // upgrade
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, String.format("Upgrading database from version %d to %d", oldVersion, newVersion));
            }
            Cursor tableCursor = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null);
            Set<String> tables = new HashSet<String>();
            while (tableCursor.moveToNext()) {
                String table = tableCursor.getString(0);
                if (!table.equals("android_metadata") && !table.equals("sqlite_sequence")) {
                    tables.add(table);
                }
            }

            for (EntityMapping mapping : Configuration.getEntities()) {
                if (tables.contains(mapping.getTable())) {
                    tables.remove(mapping.getTable());
                    Cursor columnCursor = db.rawQuery(String.format("PRAGMA table_info('%s')", mapping.getTable()), null);
                    Map<String, String> columns = new HashMap<String, String>();
                    while (columnCursor.moveToNext()) {
                        columns.put(columnCursor.getString(columnCursor.getColumnIndex("name")), columnCursor.getString(columnCursor.getColumnIndex("type")));
                        if (Log.isLoggable(TAG, Log.DEBUG)) {
                            Log.d(TAG, String.format("column: \"%s\" type: \"%s\"", columnCursor.getString(columnCursor.getColumnIndex("name")), columnCursor.getString(columnCursor.getColumnIndex("type"))));
                        }
                    }
                    for (String property : mapping.getColumns().keySet()) {
                        ColumnMapping columnMapping = mapping.getColumns().get(property);
                        String column = columnMapping.getColumn();
                        if (!columns.containsKey(column)) {
                            db.execSQL(String.format("ALTER TABLE %s ADD COLUMN %s %s", mapping.getTable(), column, columnMapping.getColumnType().getSqlType()));
                            if (Log.isLoggable(TAG, Log.DEBUG)) {
                                Log.d(TAG, String.format("Field %s added to table %s", column, mapping.getTable()));
                            }
                        }
                    }
                } else {
                    createTable(db, mapping);
                }
            }
            if (tables.size() > 0) {
                for (String tableToDrop : tables) {
                    db.execSQL(String.format("DROP TABLE IF EXISTS %s", tableToDrop));
                    if (Log.isLoggable(TAG, Log.DEBUG)) {
                        Log.d(TAG, String.format("Table %s dropped.", tableToDrop));
                    }
                }
            }
        }
    }

    // creates a new table
    private void createTable(SQLiteDatabase db, EntityMapping mapping) {
        StringBuffer sqlColumns = new StringBuffer();
        boolean isFirst = true;
        for (String property : mapping.getColumns().keySet()) {
            if (!isFirst) {
                sqlColumns.append(", ");
            }
            ColumnMapping columnMapping = mapping.getColumns().get(property);
            sqlColumns.append(columnMapping.getColumn()).append(" ");
            sqlColumns.append(columnMapping.getColumnType().getSqlType());
            if (columnMapping.isId()) {
                sqlColumns.append(" PRIMARY KEY");
                // TODO is autoincrement default?
                if (columnMapping.isAutoincrement()) {
                    sqlColumns.append(" AUTOINCREMENT");
                }
            }
            isFirst = false;
        }
        db.execSQL(String.format("CREATE TABLE IF NOT EXISTS %s (%s)", mapping.getTable(), sqlColumns.toString()));
        if (Log.isLoggable(TAG, Log.INFO)) {
            Log.i(TAG, String.format("Table %s created.", mapping.getTable()));
        }
    }
}
